package model;

import java.util.HashMap;
import java.util.UUID;

import DAO.DAOFactory;
import DAO.SessionDAO;

import common.Enums.DAOType;
import common.Enums.MethodType;
import common.Enums.ReliabilityType;
import common.Enums.SourceType;
import common.Exceptions.DAOException;
import common.Exceptions.FormulaException;
import common.Exceptions.MissingParameterException;
import common.Utils.LinkedSet;

public class CalculationMethod extends Method
{
	private Formula formula;
	private HashMap<Case, Value> cachedResults;
	private LinkedSet<Criterion> derivedMeasures;
	
	public CalculationMethod(UUID ID, String name)
	{
		super(ID, MethodType.CALCULATION);
		setName(name);
		derivedMeasures = new LinkedSet<Criterion>();
		cachedResults = new HashMap<Case, Value>();
	}
	
	/*public CalculationMethod(String name, Formula formula) throws FormulaException, DAOException
	{
		super(name, MethodType.CALCULATION);
		cachedResults = new HashMap<Case, Value>();
		this.derivedMeasures = new LinkedSet<Criterion>();
		setFormula(formula);
	}*/
	
	public CalculationMethod(UUID ID, Formula formula, LinkedSet<Criterion> derivedMeasures) throws FormulaException, DAOException
	{
		super(ID, MethodType.CALCULATION);
		cachedResults = new HashMap<Case, Value>();
		this.derivedMeasures = derivedMeasures;
		setFormula(formula);
	}
	
	public Formula getFormula() {
		return formula;
	}
	
	public void setFormulaString(String formulaString, LinkedSet<Criterion> parameters)
	{
		if(formulaString != null)
		{
			if(formula == null)
				formula = new Formula(formulaString, parameters);
			else
				formula.setFormula(formulaString, parameters);
		}
	}
	
	public LinkedSet<Criterion> getDerivedMeasures() {
		return derivedMeasures;
	}

	/*public void setDerivedMeasures(LinkedSet<Criterion> derivedMeasures) {
		this.derivedMeasures = derivedMeasures;
	}*/
	
	public Value getValue(Case caso) 
	{
		Value retVal = cachedResults.get(caso);
		
		if(retVal == null)
		{
			try
			{ retVal = generateValue(caso); }
			catch(Exception e)
			{
				StackTraceElement ste = Thread.currentThread().getStackTrace()[1];
				String location = "Class: " + ste.getClassName() + ", method: " + ste.getMethodName();
				SessionDAO.getInstance().showError(location, e.getMessage(), e);
			}
		}
		
		return retVal;
	}
	
	public Value refreshValue(Case caso) throws FormulaException, DAOException
	{
		Value retVal = cachedResults.get(caso);
		
		if(retVal == null)
			retVal = generateValue(caso);
		else
		{
			try
			{
				String updatedResult = formula.calculateResult((QuantitativeValue)retVal, caso, this.getID()).getStringResult();
				retVal.setStringResult(updatedResult);
			}
			catch(MissingParameterException mpe)
			{
				retVal = null;
			}
		}
		
		return retVal;
	}

	@Override
	public LinkedSet<Criterion> getParameters()
	{
		LinkedSet<Criterion> retVal = null;
		if(formula != null)
			retVal = formula.getParameters();
		else
			retVal = super.getParameters();
		
		return retVal;
	}
	
	@Override
	public void addParamater(Criterion parameter)
	{
		if(formula != null)
			formula.addParamater(parameter);
		else //It means that the method had been defined without a formula and the formula will be specified later...
			super.addParamater(parameter);
	}
	
	public void addDerivedMeasure(Criterion derMeasure)
	{
		derivedMeasures.add(derMeasure);
	}
	
	public void removeDerivedMeasure(Criterion derMeasure)
	{
		derivedMeasures.remove(derMeasure);
	}
	
	public boolean isParameter(Criterion criterion)
	{
		return !(derivedMeasures.contains(criterion));
	}
	
	private Value generateValue(Case caso) throws FormulaException, DAOException
	{
		Value retVal = SessionDAO.getInstance().getValue(caso.getID(), getID());
		boolean valExist = (retVal != null);
		if(!valExist)
			retVal = new QuantitativeValue();
		else if(retVal.getType() == MethodType.QUALITATIVE)
			retVal = QuantitativeValue.parseQuantitativeValue(retVal);
		
		try
		{
			retVal = formula.calculateResult((QuantitativeValue)retVal, caso, this.getID());
			
			if(!valExist)
			{
				retVal.setSource(this.getName() + ", ID: " + this.getID());
				retVal.setSourceType(SourceType.METHOD_OUTPUT);
				retVal.setReliability(ReliabilityType.DERIVED);
				retVal.setUnit("???");
				DAOFactory.getDAOFactory(DAOType.MYSQL).getValueDAO().insert(retVal);
			}
			
			cachedResults.put(caso, retVal);
		}
		catch(MissingParameterException mpe)
		{
			retVal = null;
		}
		
		return retVal;
	}
	
	public void setFormula(Formula formula) throws FormulaException, DAOException 
	{
		cachedResults = new HashMap<Case, Value>();
		this.formula = formula;
		setParameters(formula.getParameters());
		
		for(Case caso : SessionDAO.getInstance().getMethodRelatedCases(getID()))
			refreshValue(caso);
	}
}
